package com.huijie.app.sys.aspect;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.huijie.app.sys.service.ILogService;
import com.huijie.app.u.entity.Token;
import com.huijie.app.util.TokenUtil;
import com.huijie.app.sys.entity.Log;
import com.huijie.core.util.ThreadPool;
import com.huijie.core.util.http.ClientIpUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.Map;

import static org.springframework.web.bind.annotation.RequestMethod.GET;

/**
 * @ClassName PointcutSaveLog
 * @Description 切点保存日志工具类
 * @Author Alex
 * @Date 2018/10/12 14:08
 * @Version 1.0
 */
@Component
public class PointcutSaveLog {

    private static Logger log = LogManager.getLogger(PointcutSaveLog.class);

    private final ILogService logService;

    @Autowired
    public PointcutSaveLog(ILogService logService) {
        this.logService = logService;
    }

    /**
     * @param request              请求
     * @param beginTimeThreadLocal 本线程开始时间
     * @param joinPoint            切点
     * @param logType              日志类型
     * @param logException         日志异常
     * @description: 保存日志到数据库
     * @author Alex
     * @date 2018/10/17 17:25
     */
    void controllerParamSaveLog(HttpServletRequest request, ThreadLocal<Date> beginTimeThreadLocal, JoinPoint joinPoint, String logType, String logException) {
        try {
            //读取attribute中的token
            Token token = TokenUtil.getToken(request);
            if (null != token) {
                //日志标题
                String logTitle = "info".equals(logType) ? getControllerMethodDescription(joinPoint) : "异常信息";
                //日志请求url
                //String logUrl = joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()";
                String logUrl = request.getRequestURI();
                //请求方式
                String logMethod = request.getMethod();
                Object[] args = joinPoint.getArgs();
                String params = "";
                //获取请求参数集合并进行遍历拼接
                if (args.length > 0) {
                    if (GET.toString().equals(logMethod)) {
                        params = request.getQueryString();
                    } else {
                        Object object = args[0];
                        Map map = getKeyAndValue(object);
                        params = JSON.toJSONString(map);
                    }
                }
                //请求用户
                String logUserName = token.getAppId();
                //请求IP
                String logIp = ClientIpUtil.getIpAddr(request);
                //请求开始时间
                Date logStartTime = beginTimeThreadLocal.get();

                long beginTime = beginTimeThreadLocal.get().getTime();
                long endTime = System.currentTimeMillis();
                //请求耗时
                Long logElapsedTime = endTime - beginTime;
                Log log = new Log();

                log.setLogTitle(logTitle);
                log.setLogType(logType);
                log.setLogUrl(logUrl);
                log.setLogMethod(logMethod);
                log.setLogException(logException);
                log.setLogUserName(logUserName);
                log.setLogIp(logIp);
                log.setLogStartTime(LocalDateTime.ofInstant(logStartTime.toInstant(),ZoneId.systemDefault()));
                log.setLogElapsedTime(logElapsedTime);
                log.setCreator(String.valueOf(token.getUserId()));
//                log.setDeleted(0);
//                log.setCreator("");
//                log.setCreateTime(new Date());
//                log.setModifier("");
//                log.setUpdateTime(new Date());


                log.setLogParams(params);
                ThreadPool.getPool().execute(new SaveLogThread(log, logService, logIp));
            }
        } catch (Exception e1) {
            log.error("controller层切点日志保存异常", e1);
        }
    }

    /**
     * @param joinPoint 切点
     * @return java.lang.String 方法描述
     * @description: 获取注解中对方法的描述信息 用于Controller层注解
     * @author Alex
     * @date 2018/10/17 17:27
     */
    private static String getControllerMethodDescription(JoinPoint joinPoint) throws Exception {
        //获取目标类名
        String targetName = joinPoint.getTarget().getClass().getName();
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        //获取相关参数
        Object[] arguments = joinPoint.getArgs();
        //生成类对象
        Class targetClass = Class.forName(targetName);
        //获取该类中的方法
        Method[] methods = targetClass.getMethods();

        String description = "";

        for (Method method : methods) {
            if (!method.getName().equals(methodName)) {
                continue;
            }
            Class[] clazzs = method.getParameterTypes();
            //比较方法中参数个数与从切点中获取的参数个数是否相同，原因是方法可以重载哦
            if (clazzs.length != arguments.length) {
                continue;
            }
            description = method.getAnnotation(ControllerParamRecord.class).description();
        }
        return description;
    }

    /**
     * @ClassName SaveLogThread
     * @Description 保存日志子类
     * @Author Alex
     * @Date 2018/10/12 14:08
     * @Version 1.0
     */
    private static class SaveLogThread implements Runnable {
        private Log log;
        private ILogService logService;
        private String logIp;

        SaveLogThread(Log log, ILogService logService, String logIp) {
            this.log = log;
            this.logService = logService;
            this.logIp = logIp;
        }

        @Override
        public void run() {
            String logIpAddress = ClientIpUtil.getIpAddrSource(logIp);
            log.setLogIpAddress(logIpAddress);
            logService.insertOrUpdate(log);
        }
    }

    static Map<String, Object> getKeyAndValue(Object obj) {
        Map<String, Object> map = Maps.newHashMapWithExpectedSize(10);
        // 得到类对象
        Class userCla = obj.getClass();
        /* 得到类中的所有属性集合 */
        Field[] fs = userCla.getDeclaredFields();
        for (Field f : fs) {
            // 设置些属性是可以访问的
            f.setAccessible(true);
            Object val;
            try {
                // 得到此属性的值
                val = f.get(obj);
                // 设置键值
                map.put(f.getName(), val);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                e.printStackTrace();
            }

        }
        return map;
    }
}
